package com.hys.app.service.erp.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import com.hys.app.framework.exception.ServiceException;
import com.hys.app.model.erp.dos.NoGenerateRuleDO;
import com.hys.app.model.erp.dto.NoGenerateRuleItem;
import com.hys.app.model.erp.enums.NoBusinessTypeEnum;
import com.hys.app.model.erp.enums.NoGenerateItemTypeEnum;
import com.hys.app.model.erp.enums.NoSeqResetTypeEnum;
import com.hys.app.model.system.dos.DeptDO;
import com.hys.app.service.erp.NoGenerateManager;
import com.hys.app.service.erp.NoGenerateRuleManager;
import com.hys.app.service.system.DeptManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * 编号生成业务层实现
 *
 * @author 张崧
 * 2023-12-05
 */
@Service
@Slf4j
public class NoGenerateManagerImpl implements NoGenerateManager {

    @Autowired
    private NoGenerateRuleManager noGenerateRuleManager;

    @Autowired
    private DeptManager deptManager;

    @Override
    public String generate(NoBusinessTypeEnum noBusinessTypeEnum) {
        return generate(noBusinessTypeEnum, null);
    }

    @Override
    public String generate(NoBusinessTypeEnum noBusinessTypeEnum, Long deptId) {
        // 获取开启的编号生成规则
        NoGenerateRuleDO noGenerateRuleDO = noGenerateRuleManager.getOpenRule(noBusinessTypeEnum);
        if (noGenerateRuleDO == null) {
            throw new ServiceException("未找到开启的编号生成规则,请先开启");
        }

        StringBuilder builder = new StringBuilder();
        Long seq = null;
        for (int i = 0; i < noGenerateRuleDO.getItemList().size(); i++) {
            NoGenerateRuleItem noGenerateRuleItem = noGenerateRuleDO.getItemList().get(i);
            if (noGenerateRuleItem.getType() == NoGenerateItemTypeEnum.Const) {
                builder.append(noGenerateRuleItem.getConstValue());
            }
            if (noGenerateRuleItem.getType() == NoGenerateItemTypeEnum.Year) {
                builder.append(DateUtil.format(new Date(), "yyyy"));
            }
            if (noGenerateRuleItem.getType() == NoGenerateItemTypeEnum.YearMonth) {
                builder.append(DateUtil.format(new Date(), "yyyyMM"));
            }
            if (noGenerateRuleItem.getType() == NoGenerateItemTypeEnum.YearMonthDay) {
                builder.append(DateUtil.format(new Date(), "yyyyMMdd"));
            }

            if (noGenerateRuleItem.getType() == NoGenerateItemTypeEnum.Seq) {
                // 一个编号中可能包含多个顺序号，只是可能位数不同，所以这里只生成一次
                if (seq == null) {
                    seq = createSeq(noGenerateRuleDO.getId());
                }
                // 格式化顺序号的位数，不够前面补0
                String formatSeq = String.format("%0" + noGenerateRuleItem.getSeqLength() + "d", seq);
                builder.append(formatSeq);
            }

            if (noGenerateRuleItem.getType() == NoGenerateItemTypeEnum.Dept) {
                if (deptId == null) {
                    throw new ServiceException("无部门字段,但编号规则中存在部门类型,请检查编号规则");
                }
                DeptDO deptDO = deptManager.getDept(deptId);
                if (deptDO == null) {
                    throw new ServiceException("部门不存在,请重新选择部门");
                }
                builder.append(deptDO.getSn());
            }

            // 分隔符
            if (i != noGenerateRuleDO.getItemList().size() - 1) {
                builder.append(noGenerateRuleItem.getSplit());
            }
        }

        return builder.toString();
    }

    /**
     * 数据库乐观锁 + 失败重试 生成顺序号
     *
     * @param id 编号规则id
     * @return 顺序号
     */
    private Long createSeq(Long id) {
        NoGenerateRuleDO ruleDO = noGenerateRuleManager.getById(id);
        // 如果需要重置，则使用起始值，否则使用递增的顺序号
        Long currSeqNumber = needReset(ruleDO) ? ruleDO.getSeqBeginNumber() : ruleDO.getCurrSeqNumber();
        // 顺序号+1
        ruleDO.setCurrSeqNumber(currSeqNumber + 1);
        // 最后一次生成顺序号的时间
        ruleDO.setSeqLastGenerateTime(new Date());
        boolean updateResult = noGenerateRuleManager.updateById(ruleDO);
        // 乐观锁更新成功，则直接返回结果
        if (updateResult) {
            return currSeqNumber;
        } else {
            // 乐观锁更新失败进行重试
            // 数据库引擎必须使用MyISAM，让这张表的事务失效，否则事务隔离级别为可重复读,读取的数据永远是旧数据
            log.debug("编号规则：{}乐观锁更新失败，进行重试", id);
            ThreadUtil.sleep(200);
            return createSeq(id);
        }

    }

    /**
     * 判断这一时刻是否需要重置顺序号
     *
     * @param ruleDO 编号规则
     * @return true：需要重置顺序号
     */
    private boolean needReset(NoGenerateRuleDO ruleDO) {
        // 重置类型
        NoSeqResetTypeEnum seqResetType = ruleDO.getSeqGenerateType();

        // 不重置
        if (seqResetType == NoSeqResetTypeEnum.Normal) {
            return false;
        }

        String formatDate;
        if (seqResetType == NoSeqResetTypeEnum.Year) {
            formatDate = "yyyy";
        } else if (seqResetType == NoSeqResetTypeEnum.Month) {
            formatDate = "yyyyMM";
        } else {
            formatDate = "yyyyMMdd";
        }

        // 当前时间的日期
        String currDateInfo = DateUtil.format(new Date(), formatDate);
        // 最后一次生成顺序号的日期
        String lastDateInfo = DateUtil.format(ruleDO.getSeqLastGenerateTime(), formatDate);

        // 如果不相等，则代表是今天（今月/今年）第一次生成，需要重置顺序号
        return !currDateInfo.equals(lastDateInfo);
    }

}

